package com.example.demo.jackson;

import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMappingJacksonResponseBodyAdvice;

import java.util.concurrent.ConcurrentHashMap;

@ControllerAdvice
public class JacksonViewResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {

    static final String JACKSON_VIEW_FILTER_ID = "class_and_property_filter";

    private final ConcurrentHashMap<MethodParameter, SimpleFilterProvider> providerCache = new ConcurrentHashMap<>();

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return super.supports(returnType, converterType) && returnType.hasMethodAnnotation(JacksonView.class);
    }

    @Override
    protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
        SimpleFilterProvider provider = providerCache.computeIfAbsent(returnType, this::createFilters);
        bodyContainer.setFilters(provider);
    }

    private SimpleFilterProvider createFilters(MethodParameter returnType) {
        JacksonView jacksonView = returnType.getMethodAnnotation(JacksonView.class);
        Assert.state(jacksonView != null, "No JacksonView annotation");

        TypeBeanPropertyFilter.TypeBeanPropertyFilterBuilder builder = TypeBeanPropertyFilter.builder();
        for (JacksonFilter filter : jacksonView.filters()) {
            builder.excludeClassProperties(filter.type(), filter.exclude());
        }

        SimpleFilterProvider provider = new SimpleFilterProvider();
        provider.addFilter(JACKSON_VIEW_FILTER_ID, builder.build());
        return provider;
    }

}
